#include "CRetroInput.h"
#include <stdio.h>
#include <assert.h>

// TODO: uncouple this and CGame
#include "../CGame.h"

#define ERRORCHECK(x) if(FAILED(x)){ return false;}
#define _RELEASE(x) if(x) { x->Release(); x = 0; }
#define RELEASE(x) if(x) { x->Unacquire(); x->Release(); x = 0; }

CRetroInput* CRetroInput::m_pInstance = NULL;
CRetroInput::CRetroInput(void)
{
	m_pInput = NULL;
	m_pKeyboard = NULL;
	m_pMouse = NULL;

	memset(&m_keyboardState, 0, sizeof(BYTE) * 256);
	memset(&m_mouseState, 0, sizeof(DIMOUSESTATE));
	memset(&m_wiimoteState, 0, sizeof(WORD));
}

CRetroInput::~CRetroInput(void)
{
	Shutdown();
}
void CRetroInput::wiiremote_callback(wiimote &remote, state_change_flags changed, const wiimote_state &new_state)
{
	if(changed & CONNECTED)
	{
		// set the report mode  to include extentions
		remote.SetReportType(wiimote::IN_BUTTONS_ACCEL_EXT);
	}
	if(changed & CONNECTION_LOST)
	{
		// TODO: uncouple RetroInput and CGame
		CGame::GetInstance()->SetCursorDevicePriority(RZ_MOUSE);
	}
	if(changed & MOTIONPLUS_DETECTED)
	{
		if(remote.ExtensionType == wiimote_state::NONE) 
		{
			remote.EnableMotionPlus();
			// TODO: uncouple RetroInput and CGame
			CGame::GetInstance()->SetCursorDevicePriority(RZ_WIIMOTE);
		}
	}
	// an extension is connected to the MotionPlus
	else if(changed & MOTIONPLUS_EXTENSION_CONNECTED)
	{
		// We can't read it if the MotionPlus is currently enabled, so disable it:
		if(remote.MotionPlusEnabled())
		{
			remote.DisableMotionPlus();
		}
	}
	// an extension disconnected from the MotionPlus
	else if(changed & MOTIONPLUS_EXTENSION_DISCONNECTED)
	{
		// enable the MotionPlus data again:
		if(remote.MotionPlusConnected())
		{
			remote.EnableMotionPlus();
		}
	}
	
	
	// another extension was just connected:
	else if(changed & EXTENSION_CONNECTED)
	{
		remote.SetReportType(wiimote::IN_BUTTONS_ACCEL_EXT);
	}
	// extension was just disconnected:
	else if(changed & EXTENSION_DISCONNECTED)
	{
		remote.SetReportType(wiimote::IN_BUTTONS_ACCEL);
	}
	
}

bool CRetroInput::Init(HWND hWnd, HINSTANCE hInstance, BYTE deviceFlags, BYTE exclusiveFlags)
{
	
	HRESULT error = 0;
	m_DevicesInUse = deviceFlags;

	// init the main interface
	error = DirectInput8Create(hInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_pInput, NULL);
	ERRORCHECK(error);

	// init the keyboard
	if(deviceFlags & RZ_KEYBOARD)
	{
		error = m_pInput->CreateDevice(GUID_SysKeyboard, &m_pKeyboard, NULL);
		ERRORCHECK(error);
		error = m_pKeyboard->SetDataFormat(&c_dfDIKeyboard);
		ERRORCHECK(error);

		// handle if exclusive
		BYTE flag = DISCL_FOREGROUND;
		if(exclusiveFlags & RZ_KEYBOARD)
		{
			flag |= DISCL_EXCLUSIVE;
		}
		else
		{
			flag |= DISCL_NONEXCLUSIVE;
		}
		error = m_pKeyboard->SetCooperativeLevel(hWnd, flag);
		ERRORCHECK(error);
		error = m_pKeyboard->Acquire();
		ERRORCHECK(error);
	}

	// init the mouse
	if(deviceFlags & RZ_MOUSE)
	{
		error = m_pInput->CreateDevice(GUID_SysMouse, &m_pMouse, NULL);
		ERRORCHECK(error);
		error = m_pMouse->SetDataFormat(&c_dfDIMouse);
		ERRORCHECK(error);
		// handle if exclusive
		BYTE flag = DISCL_FOREGROUND;
		if(exclusiveFlags & RZ_MOUSE)
		{
			flag |= DISCL_EXCLUSIVE;
		}
		else
		{
			flag |= DISCL_NONEXCLUSIVE;
		}
		error = m_pMouse->SetCooperativeLevel(hWnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
		ERRORCHECK(error);
		error = m_pMouse->Acquire();
		ERRORCHECK(error);
	}

	// prepare to look for a wii remote
	if(deviceFlags & RZ_WIIMOTE)
	{
		m_Wiimote.ChangedCallback = CRetroInput::wiiremote_callback;
		m_Wiimote.CallbackTriggerFlags = static_cast<state_change_flags>(CONNECTED | CONNECTION_LOST | EXTENSION_DISCONNECTED | NUNCHUK_CONNECTED | MOTIONPLUS_CHANGED);
		
		m_vfMotionPlusCalibrationOffset.x = 0.0f;
		m_vfMotionPlusCalibrationOffset.y = 0.0f;
		m_vfMotionPlusCalibrationOffset.z = 0.0f;
	
	}

	return true;
}

void CRetroInput::Shutdown()
{
	RELEASE(m_pMouse);
	RELEASE(m_pKeyboard);
	_RELEASE(m_pInput);
}

void CRetroInput::Update()
{
	HRESULT error;

	// read the keyboard
	if(m_DevicesInUse & RZ_KEYBOARD)
	{
		memcpy(&m_prevKeyboardState, &m_keyboardState, sizeof(m_keyboardState));
		error = m_pKeyboard->GetDeviceState(sizeof(m_keyboardState), (void**)&m_keyboardState);
		if(FAILED(error))
		{
			// attempt to re-acquire the keyboard
			if(error == DIERR_INPUTLOST || error == DIERR_NOTACQUIRED)
			{
				m_pKeyboard->Acquire();
			}
		}
	}

	// read the mouse
	if(m_DevicesInUse & RZ_MOUSE)
	{
		memcpy(&m_prevMouseState, &m_mouseState, sizeof(DIMOUSESTATE));
		error = m_pMouse->GetDeviceState(sizeof(DIMOUSESTATE), (void**)&m_mouseState);
		if(FAILED(error))
		{
			// attempt to re-acquire the mouse
			if(error == DIERR_INPUTLOST || error == DIERR_NOTACQUIRED)
			{
				m_pMouse->Acquire();
			}
		}
	}

	// read the wiimote
	if(m_DevicesInUse & RZ_WIIMOTE)
	{
		if(m_Wiimote.IsConnected() == false)
		{
			// TODO: Do this sexier
			// try to connect a wii remote
			bool bConnected = m_Wiimote.Connect(1);//wiimote::FIRST_AVAILABLE);
			if(bConnected)
			{
				// set only the first LED
				m_Wiimote.SetLEDs(0x1);
				m_Wiimote.RumbleForAsync(500); // physical feedback
				memset(&m_wiimoteState, 0, sizeof(WORD));
			}
		}
		else
		{
			m_Wiimote.RefreshState();

			// get the wii remote buttons in a buffer
			memcpy_s(&m_prevWiimoteState, sizeof(WORD), &m_wiimoteState, sizeof(WORD)); // prev buffer
			memcpy_s(&m_wiimoteState, sizeof(WORD), &m_Wiimote.Button.Bits, sizeof(WORD)); // curr buffer

			// get, if any, the nunchuck buttons
			if(m_Wiimote.NunchukConnected())
			{
				if(!m_Wiimote.MotionPlusEnabled())
				{
					if(m_Wiimote.Nunchuk.C)
					{
						m_wiimoteState |= WII_BTN_NUNCHUCK_C;
					}
					if(m_Wiimote.Nunchuk.Z)
					{
						m_wiimoteState |= WII_BTN_NUNCHUCK_Z;
					}
				}
			}
		}
	}
}

void CRetroInput::RegisterAction(ButtonAction actionID, InputDevices actionDevice, ButtonState actionState, UINT keycode)
{
	ActionData action;
	action.keycode = keycode;
	action.state = actionState;
	m_ActionMap[actionID][actionDevice] = action;
}

void CRetroInput::UnRegisterAction(ButtonAction actionID, InputDevices actionDevice)
{
	m_ActionMap[actionID].erase(actionDevice);
}

bool CRetroInput::CheckAction(ButtonAction actionID)
{
	std::map<InputDevices, ActionData> &submap = m_ActionMap[actionID];
	std::map<InputDevices, ActionData>::iterator submapIterator = submap.begin();
	bool actionResult = false;
	for(;submapIterator != submap.end(); ++submapIterator)
	{
		ActionData &data = submapIterator->second;

		switch(submapIterator->first)
		{
		case RZ_MOUSE:
			{
				if(m_DevicesInUse & RZ_MOUSE)
					actionResult = actionResult || CheckMouseState(data);
			} break;
		case RZ_KEYBOARD:
			{
				if(m_DevicesInUse & RZ_KEYBOARD)
					actionResult = actionResult || CheckKeyboardState(data);
			} break;
		case RZ_WIIMOTE:
			{
				if(m_DevicesInUse & RZ_WIIMOTE)
					actionResult = actionResult || CheckWiimoteState(data);
			} break;
		case RZ_NUNCHUCK:
			{
				if(m_DevicesInUse & RZ_NUNCHUCK)
					actionResult = actionResult || CheckWiimoteState(data);
			}
		default:
			{
				assert("Invalid device type!");
			}
		};
	}
	return actionResult;
}

bool CRetroInput::CheckMouseState(ActionData &data)
{
	switch(data.state)
	{
	case BTN_PRESSED:
		{
			return MousePressed(data.keycode);
		} break;
	case BTN_RELEASED:
		{
			return MouseReleased(data.keycode);
		} break;
	case BTN_HELD:
		{
			return MouseHeld(data.keycode);
		} break;
	default:
		{
			assert("Invalid state!");
		}
	}
	return false;
}

bool CRetroInput::CheckKeyboardState(ActionData &data)
{
	switch(data.state)
	{
	case BTN_PRESSED:
		{
			return KeyPressed(data.keycode);
		} break;
	case BTN_RELEASED:
		{
			return KeyReleased(data.keycode);
		} break;
	case BTN_HELD:
		{
			return KeyHeld(data.keycode);
		} break;
	default:
		{
			assert("Invalid state!");
		}
	}
	return false;
}

bool CRetroInput::CheckWiimoteState(ActionData &data)
{
	switch(data.state)
	{
	case BTN_PRESSED:
		{
			return WiiKeyPressed(data.keycode);
		} break;
	case BTN_RELEASED:
		{
			return WiiKeyReleased(data.keycode);
		} break;
	case BTN_HELD:
		{
			return WiiKeyHeld(data.keycode);
		} break;
	default:
		{
			assert("Invalid state!");
		}
	}
	return false;
}

bool CRetroInput::KeyHeld(UINT keycode)
{
	return (m_keyboardState[keycode] & 0x80) != 0;
}

bool CRetroInput::KeyPressed(UINT keycode)
{
	bool last = (m_prevKeyboardState[keycode] & 0x80) != 0;
	bool cur = (m_keyboardState[keycode] & 0x80) != 0;

	return !last && cur;
}

bool CRetroInput::KeyReleased(UINT keycode)
{
	bool last = (m_prevKeyboardState[keycode] & 0x80) != 0;
	bool cur = (m_keyboardState[keycode] & 0x80) != 0;

	return last && !cur;
}

vec3<long> CRetroInput::GetMouseDelta()
{
	vec3<long> delta;
	delta.x = m_mouseState.lX;
	delta.y = m_mouseState.lY;
	delta.z = m_mouseState.lZ;

	return delta;
}

bool CRetroInput::MouseHeld(UINT keycode)
{
	return (m_mouseState.rgbButtons[keycode] & 0x80) != 0;
}

bool CRetroInput::MousePressed(UINT keycode)
{
	bool last = (m_prevMouseState.rgbButtons[keycode] & 0x80) != 0;
	bool cur = (m_mouseState.rgbButtons[keycode] & 0x80) != 0;

	return !last && cur;
}

bool CRetroInput::MouseReleased(UINT keycode)
{
	bool last = (m_prevMouseState.rgbButtons[keycode] & 0x80) != 0;
	bool cur = (m_mouseState.rgbButtons[keycode] & 0x80) != 0;

	return last && !cur;
}

vec3<float> CRetroInput::GetWiiAccelerometerDelta()
{
	vec3<float> delta;
	delta.x = (m_Wiimote.Acceleration.X);
	delta.y = (m_Wiimote.Acceleration.Y);
	delta.z = (m_Wiimote.Acceleration.Z);

	return delta;
}

vec3<float> CRetroInput::GetWiiMotionPlusDelta(bool bOffset)
{
	if(m_Wiimote.MotionPlusConnected())
	{
		m_vfMotionPlusDelta.x = (m_Wiimote.MotionPlus.Speed.Pitch);
		m_vfMotionPlusDelta.y = (m_Wiimote.MotionPlus.Speed.Yaw);
		m_vfMotionPlusDelta.z = (m_Wiimote.MotionPlus.Speed.Roll);

		// use the calibration offset
		if(bOffset)
		{
			m_vfMotionPlusDelta -= m_vfMotionPlusCalibrationOffset;
		}
	}
	return m_vfMotionPlusDelta;
}

void CRetroInput::SetMotionPlusCalibrationOffset(float X, float Y, float Z)
{
	m_vfMotionPlusCalibrationOffset.x = X;
	m_vfMotionPlusCalibrationOffset.y = Y;
	m_vfMotionPlusCalibrationOffset.z = Z;
}

void CRetroInput::SetMotionPlusCalibrationOffset(vec3<float>& offset)
{
	SetMotionPlusCalibrationOffset(offset.x, offset.y, offset.z);
}

bool CRetroInput::WiiKeyHeld(UINT keycode)
{
	return (m_wiimoteState & keycode) != 0;
}

bool CRetroInput::WiiKeyPressed(UINT keycode)
{
	bool last = (m_prevWiimoteState & keycode) != 0;
	bool cur = (m_wiimoteState & keycode) != 0;

	return !last && cur;
}

bool CRetroInput::WiiKeyReleased(UINT keycode)
{
	bool last = (m_prevWiimoteState & keycode) != 0;
	bool cur = (m_wiimoteState & keycode) != 0;

	return last && !cur;
}
